<template>
  <div class="speech-container">
    <!-- 识别结果区域 -->
    <div class="result-panel">
      <h2><i class="el-icon-microphone"></i> 实时语音识别（流式）</h2>
      <el-input
        type="textarea"
        v-model="recognitionResult"
        :rows="8"
        placeholder="正在等待语音输入..."
        readonly
        class="result-textarea"
      ></el-input>
      <div class="status-bar">
        <el-tag :type="statusType">{{ currentStatus }}</el-tag>
        <div class="debug-info" v-if="debugInfo">{{ debugInfo }}</div>
      </div>
    </div>

    <!-- 控制按钮 -->
    <div class="control-panel">
      <el-button
        type="primary"
        icon="el-icon-play"
        @click="connectWebSocket"
        :disabled="isConnected"
        size="large"
      >
        开始连接
      </el-button>
      <el-button
        type="success"
        icon="el-icon-microphone"
        @click="startRecording"
        :disabled="!isConnected || isRecording"
        size="large"
      >
        开始录音
      </el-button>
      <el-button
        type="danger"
        icon="el-icon-stop"
        @click="stopRecording"
        :disabled="!isRecording"
        size="large"
      >
        停止录音
      </el-button>
      <el-button
        type="warning"
        icon="el-icon-close"
        @click="disconnectWebSocket"
        :disabled="!isConnected"
        size="large"
      >
        断开连接
      </el-button>
    </div>

    <!-- 配置区域 -->
    <div class="config-panel">
      <el-card>
        <div slot="header">阿里云流式识别配置</div>
        <el-form :inline="true" class="config-form" :model="configForm">
          <el-form-item label="AppKey" prop="appKey">
            <el-input
              v-model="configForm.appKey"
              placeholder="请输入阿里云AppKey"
              style="width: 220px"
            ></el-input>
          </el-form-item>
          <el-form-item label="Token" prop="token">
            <el-input
              v-model="configForm.token"
              placeholder="请输入有效Token"
              style="width: 300px"
            ></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="success" @click="testConfig">测试配置</el-button>
          </el-form-item>
        </el-form>
      </el-card>
    </div>
  </div>
</template>

<script>
// 生成32位小写十六进制字符串（符合阿里云message_id/task_id要求）
function generate32Hex() {
  const hexChars = "0123456789abcdef";
  let result = "";
  for (let i = 0; i < 32; i++) {
    result += hexChars[Math.floor(Math.random() * 16)];
  }
  return result;
}

export default {
  name: "AliyunStreamSpeechTranscriber",
  data() {
    return {
      // 配置表单
      configForm: {
        appKey: "r8VfaikoBjTPFZhK", // 填写你的阿里云AppKey
        token: "25ddf42ecac24bc08d5caa9ca6f3b369", // 填写有效流式识别Token
      },

      // 识别状态
      recognitionResult: "",
      currentStatus: "未连接",
      statusType: "info",
      debugInfo: "",
      isConnected: false,
      isRecording: false,

      // WebSocket相关
      websocket: null,
      taskId: "",

      // 音频处理相关
      audioContext: null,
      audioStream: null,
      audioInput: null,
      scriptProcessor: null,
      silenceTimer: null,
      lastSendTime: 0,
    };
  },
  methods: {
    // 连接WebSocket
    connectWebSocket() {
      const { appKey, token } = this.configForm;
      if (!appKey || !token) {
        this.$message.error("请先填写AppKey和Token！");
        return;
      }

      this.recognitionResult = "";
      this.currentStatus = "连接中...";
      this.statusType = "warning";
      this.debugInfo = "建立WebSocket连接...";

      const socketUrl = `wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1?token=${encodeURIComponent(
        token
      )}`;
      try {
        this.websocket = new WebSocket(socketUrl);

        this.websocket.onopen = () => {
          this.isConnected = true;
          this.currentStatus = "已连接（待录音）";
          this.statusType = "success";
          this.debugInfo = "发送开始识别指令...";

          this.taskId = generate32Hex();
          const startTransMsg = {
            header: {
              appkey: appKey,
              namespace: "SpeechTranscriber",
              name: "StartTranscription",
              task_id: this.taskId,
              message_id: generate32Hex(),
            },
            payload: {
              format: "pcm",
              sample_rate: 16000,
              enable_intermediate_result: true,
              enable_punctuation_prediction: true,
              enable_inverse_text_normalization: true,
            },
          };
          this.websocket.send(JSON.stringify(startTransMsg));
        };

        this.websocket.onmessage = (event) => {
          this.debugInfo = `收到消息：${event.data}...`;
          try {
            const msg = JSON.parse(event.data);
            const { name } = msg.header;

            if (name === "TranscriptionStarted") {
              this.debugInfo = "流式识别就绪，可开始录音";
              this.$message.success("可开始录音！");
            } else if (name === "TranscriptionResultChanged") {
              if (msg.payload.result) {
                this.recognitionResult = msg.payload.result;
                this.debugInfo = `中间结果：${msg.payload.result.slice(
                  0,
                  20
                )}...`;
              }
            } else if (name === "TranscriptionCompleted") {
              this.recognitionResult = msg.payload.result || "无结果";
              this.debugInfo = "识别完成，获取最终结果";
              this.$message.success("识别完成！");
            } else if (name === "TaskFailed") {
              const errorMsg = `失败[${msg.header.status}]：${msg.header.status_text}`;
              this.debugInfo = errorMsg;
              this.$message.error(errorMsg);
              this.disconnectWebSocket();
            }
          } catch (e) {
            // this.debugInfo = `解析失败：${e.message}`;
            this.$message.warning(`消息解析异常`);
          }
        };

        this.websocket.onerror = (error) => {
          this.debugInfo = `WebSocket错误：${error.message}`;
          this.currentStatus = "连接错误";
          this.statusType = "error";
          this.isConnected = false;
          this.$message.error(`连接错误：${error.message}`);
        };

        this.websocket.onclose = (event) => {
          this.isConnected = false;
          this.isRecording = false;
          this.currentStatus = "已断开";
          this.statusType = "info";
          this.debugInfo = `关闭（代码：${event.code}，原因：${
            event.reason || "正常"
          }）`;
          this.cleanupAudioResources();
        };
      } catch (e) {
        this.$message.error(`连接失败：${e.message}`);
        this.currentStatus = "连接失败";
        this.statusType = "error";
      }
    },

    // 开始录音
    startRecording() {
      if (!this.isConnected || this.isRecording) return;

      this.isRecording = true;
      this.currentStatus = "请求麦克风权限...";
      this.debugInfo = "正在请求麦克风权限，请在浏览器弹窗中允许...";

      // 修复1：放宽音频约束（exact改为ideal，避免设备不支持导致失败）
      const audioConstraints = {
        audio: {
          sampleRate: { ideal: 16000 }, // 关键：使用ideal而非exact，提高兼容性
          channelCount: { ideal: 1 }, // 单声道（理想值）
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: true,
        },
      };

      // 修复2：确保在正确的上下文调用，并增加详细错误处理
      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
        this.handleRecordingError("当前浏览器不支持麦克风访问（请升级浏览器）");
        return;
      }

      // 修复3：直接通过navigator.mediaDevices调用，确保上下文正确
      navigator.mediaDevices
        .getUserMedia(audioConstraints)
        .then((stream) => {
          this.audioStream = stream;
          // 检查实际采样率（可选）
          const track = stream.getAudioTracks()[0];
          const settings = track.getSettings();
          this.debugInfo = `麦克风已激活（实际采样率：${settings.sampleRate}Hz）`;
          this.initAudioContext();
        })
        .catch((error) => {
          // 修复4：详细分类错误，提供明确指引
          let errorMsg = "";
          switch (error.name) {
            case "NotAllowedError":
              errorMsg =
                "麦克风权限被拒绝，请在浏览器地址栏点击锁形图标开启权限";
              break;
            case "NotFoundError":
              errorMsg = "未检测到麦克风设备，请连接麦克风后重试";
              break;
            case "NotReadableError":
              errorMsg = "麦克风被占用或无法访问，请关闭其他占用麦克风的程序";
              break;
            case "OverconstrainedError":
              errorMsg =
                "当前设备不支持所需的音频参数（建议使用16000Hz采样率的麦克风）";
              break;
            default:
              errorMsg = `录音失败：${error.name} - ${error.message}`;
          }
          this.handleRecordingError(errorMsg);
        });
    },

    // 新增：统一错误处理方法
    handleRecordingError(message) {
      this.isRecording = false;
      this.currentStatus = "录音失败";
      this.debugInfo = message;
      this.$message.error(message);
    },
    initAudioContext() {
      try {
        this.audioContext = new (window.AudioContext ||
          window.webkitAudioContext)({
          sampleRate: 16000,
        });

        this.audioInput = this.audioContext.createMediaStreamSource(
          this.audioStream
        );
        this.scriptProcessor = this.audioContext.createScriptProcessor(
          2048,
          1,
          1
        );

        this.lastSendTime = Date.now();
        this.scriptProcessor.onaudioprocess = (event) => {
          if (!this.isConnected || !this.isRecording) return;

          const inputData = event.inputBuffer.getChannelData(0);
          const inputData16 = new Int16Array(inputData.length);
          for (let i = 0; i < inputData.length; i++) {
            inputData16[i] = Math.max(-1, Math.min(1, inputData[i])) * 0x7fff;
          }

          if (this.websocket.readyState === WebSocket.OPEN) {
            this.websocket.send(inputData16.buffer);
            this.lastSendTime = Date.now();
          }
        };

        this.audioInput.connect(this.scriptProcessor);
        this.scriptProcessor.connect(this.audioContext.destination);
        this.currentStatus = "录音中...";
        this.$message.success("录音已开始，正在实时识别");
      } catch (e) {
        this.handleRecordingError(`音频初始化失败：${e.message}`);
      }
    },

    // 停止录音
    stopRecording() {
      if (!this.isRecording) return;

      this.isRecording = false;
      this.currentStatus = "已停止录音（待结果）";
      this.statusType = "info";
      this.debugInfo = "清理音频资源...";
      this.cleanupAudioResources();

      if (this.isConnected && this.websocket.readyState === WebSocket.OPEN) {
        const stopTransMsg = {
          header: {
            appkey: this.configForm.appKey,
            namespace: "SpeechTranscriber",
            name: "StopTranscription",
            task_id: this.taskId,
            message_id: generate32Hex(),
          },
          payload: {},
        };
        this.websocket.send(JSON.stringify(stopTransMsg));
        this.debugInfo = "发送停止指令，等待最终结果...";
      }
    },

    // 断开WebSocket
    disconnectWebSocket() {
      if (this.isRecording) this.stopRecording();
      if (this.websocket) this.websocket.close(1000, "主动断开");
      this.isConnected = false;
      this.currentStatus = "已断开";
      this.statusType = "info";
    },

    // 清理音频资源
    cleanupAudioResources() {
      if (this.silenceTimer) {
        clearInterval(this.silenceTimer);
        this.silenceTimer = null;
      }
      if (this.scriptProcessor) {
        this.scriptProcessor.disconnect();
        this.scriptProcessor = null;
      }
      if (this.audioInput) {
        this.audioInput.disconnect();
        this.audioInput = null;
      }
      if (this.audioStream) {
        this.audioStream.getTracks().forEach((track) => track.stop());
        this.audioStream = null;
      }
      if (this.audioContext) {
        this.audioContext.close().then(() => (this.audioContext = null));
      }
    },

    // 静音补帧（防超时断开）
    checkSilenceAndSendFrame() {
      if (this.silenceTimer) return;
      this.silenceTimer = setInterval(() => {
        const now = Date.now();
        if (
          now - this.lastSendTime > 500 &&
          this.isConnected &&
          this.isRecording
        ) {
          const zeroFrame = new Int16Array(320);
          this.websocket.send(zeroFrame.buffer);
          this.lastSendTime = now;
          this.debugInfo = "发送静音帧（维持连接）";
        }
      }, 300);
    },

    // 测试配置
    testConfig() {
      const { appKey, token } = this.configForm;
      if (!appKey) {
        this.$message.error("AppKey不能为空！");
        return;
      }
      if (!token) {
        this.$message.error("Token不能为空！");
        return;
      }
      if (token.length !== 32) {
        this.$message.warning("Token格式异常（通常为32位）！");
        return;
      }
      this.$message.success("配置格式检查通过！");
    },
  },

  beforeDestroy() {
    this.disconnectWebSocket();
    this.cleanupAudioResources();
  },
};
</script>

<style scoped>
.speech-container {
  max-width: 1000px;
  margin: 20px auto;
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}

.result-panel {
  margin-bottom: 24px;
}

.result-panel h2 {
  margin: 0 0 16px;
  color: #333;
  font-size: 18px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.result-textarea {
  width: 100%;
  min-height: 140px;
  margin-bottom: 12px;
  font-size: 14px;
  line-height: 1.6;
}

.status-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  color: #666;
}

.debug-info {
  max-width: 70%;
  /* white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis; */
}

.control-panel {
  display: flex;
  gap: 12px;
  justify-content: flex-start;
  flex-wrap: wrap;
  margin-bottom: 24px;
}

.config-panel {
  background-color: #fafafa;
  padding: 16px;
  border-radius: 6px;
}

.config-form {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
}

@media (max-width: 768px) {
  .control-panel {
    justify-content: center;
  }
  .config-form {
    flex-direction: column;
    align-items: stretch;
  }
  .debug-info {
    max-width: 50%;
  }
}
</style>
